Popular Searches
Popular Course Categories
Popular Courses

Using Diffable Data Sources for Efficient UI Updates

Using Diffable Data Sources for Efficient UI Updates

Lists, Collections, and Dynamic Data Presentation

Using Diffable Data Sources for Efficient UI Updates

Course: iOS App Development

Topic: Using Diffable Data Sources for Efficient UI Updates

Course Link: iOS Training at JustAcademy

Demo Link: Register for a Free Course Demo

Website: JustAcademy Official Website


1. Introduction to Diffable Data Sources

In iOS development, table views and collection views are commonly used to display dynamic data such as contacts, products, photos, messages, tasks, and notifications. These interfaces often need to change when data is inserted, deleted, moved, filtered, or updated.

Traditionally, developers used the UITableViewDataSource or UICollectionViewDataSource protocols and manually coordinated updates to the interface. When the data model and the visible rows or items became inconsistent, applications could encounter errors such as invalid update exceptions or unexpected animations.

Diffable data sources provide a modern way to manage these changes. Instead of manually calculating every insertion, deletion, and movement, developers describe the desired state of the interface using a snapshot. UIKit compares the current state with the new state and determines the required changes.

Diffable data sources are available for both table views and collection views. The UIKit APIs include UITableViewDiffableDataSource, UICollectionViewDiffableDataSource, and NSDiffableDataSourceSnapshot.

Learning Objectives

  • Understand the purpose and advantages of diffable data sources.
  • Compare traditional data sources with diffable data sources.
  • Understand identifiers, sections, items, and snapshots.
  • Configure a diffable data source for a collection view.
  • Apply snapshots to insert, delete, move, and update items.
  • Implement search, filtering, and item refresh operations.
  • Identify common errors and follow performance best practices.

2. What Is a Diffable Data Source?

A diffable data source is an object that manages the relationship between your data identifiers and the items displayed in a table view or collection view. It automatically calculates the differences between the current UI state and a new snapshot.

For example, imagine a shopping application displaying five products. A user removes one product, adds another, and changes the order of two products. With a traditional data source, the developer may need to coordinate several manual updates. With a diffable data source, the developer creates a snapshot describing the updated product list and applies it.

How It Works

  1. Prepare the data: Obtain the latest model objects from the application’s data source.
  2. Identify the items: Use stable, unique identifiers for each item.
  3. Create a snapshot: Describe the sections and item identifiers that should appear.
  4. Apply the snapshot: Submit the new state to the diffable data source.
  5. Update the interface: UIKit determines the differences and updates the visible items.

Traditional vs. Diffable Data Sources

Feature Traditional Data Source Diffable Data Source
Data management Developer supplies counts and cells and coordinates updates. Data source manages displayed item identifiers and snapshots.
Insertions and deletions Often require manual insert/delete calls and careful coordination. Calculated from the old and new snapshots.
Moving items Developer coordinates data changes and UI movement. Changes in item order are reflected by applying a new snapshot.
Update safety Incorrect update counts can cause runtime exceptions. Snapshot-based updates reduce common synchronization errors.
Animation Must be coordinated with manual batch updates. Can animate changes when applying snapshots.
Item identity Often managed using array positions or custom logic. Uses stable identifiers to track items across updates.

3. Understanding the Main Components

3.1 UITableViewDiffableDataSource

UITableViewDiffableDataSource connects a diffable data source to a UITableView. It provides the cells for the table view and tracks which item identifiers belong to each section.

Use it when displaying vertically arranged information such as settings, messages, contact lists, transaction histories, or task lists.

3.2 UICollectionViewDiffableDataSource

UICollectionViewDiffableDataSource performs the same role for a UICollectionView. It is useful for product grids, photo galleries, dashboards, category layouts, and other flexible interfaces.

3.3 NSDiffableDataSourceSnapshot

A snapshot represents the intended state of a table view or collection view at a particular point in time. It contains section identifiers and item identifiers in the desired order.

Common snapshot operations include:

  • appendSections(_:) — adds section identifiers.
  • appendItems(_:toSection:) — adds item identifiers to a section.
  • deleteItems(_:) — removes item identifiers.
  • deleteSections(_:) — removes entire sections.
  • moveItem(_:beforeItem:) and related movement methods — change item order.
  • reloadItems(_:) — requests that specified items reload.
  • reconfigureItems(_:) — refreshes existing item content while preserving the cell where supported.

3.4 Identifiers

Identifiers tell the diffable data source which logical item is being displayed. Section and item identifier types must conform to Hashable. Each identifier must be unique within its respective section or item collection in a snapshot.

For example, a product can have a UUID that remains the same even when its name or price changes. A stable identifier helps UIKit recognize that the product is the same item rather than a new one.

struct Product: Hashable {
    let id: UUID
    var name: String
    var price: Double
}

For production applications, it is often best to use a stable identifier such as a database primary key, server ID, or UUID. Avoid generating a new ID every time you refresh an item, because that makes the data source treat the item as a different item.

4. Setting Up a Collection View with a Diffable Data Source

This example builds a simple product collection using UIKit and Swift. It assumes that your view controller already has a configured UICollectionView named collectionView, with a cell registration or a registered cell class.

Step 1: Define the Model

import UIKit

struct Product: Hashable {
    let id: UUID
    var name: String
    var price: Double
}

The model contains a unique identifier, a product name, and a price. The identifier is used to track the product in snapshots.

Step 2: Define a Section Identifier

enum Section {
    case main
}

Using an enum for sections is convenient when the screen has a small, known set of sections. Each section case is a distinct identifier.

Step 3: Declare the Data Source

private var dataSource:
    UICollectionViewDiffableDataSource<Section, UUID>!

private var products: [UUID: Product] = [:]

The data source uses Section as the section identifier type and UUID as the item identifier type. The dictionary provides the model associated with each product ID.

Step 4: Configure the Data Source

private func configureDataSource() {
    let registration = UICollectionView.CellRegistration<
        UICollectionViewListCell, UUID
    > { [weak self] cell, indexPath, productID in

        guard let product = self?.products[productID] else {
            return
        }

        var content = cell.defaultContentConfiguration()
        content.text = product.name
        content.secondaryText = String(
            format: "$%.2f", product.price
        )
        cell.contentConfiguration = content
    }

    dataSource = UICollectionViewDiffableDataSource<
        Section, UUID
    >(collectionView: collectionView) {
        collectionView, indexPath, productID in

        collectionView.dequeueConfiguredReusableCell(
            using: registration,
            for: indexPath,
            item: productID
        )
    }
}

This code creates a cell registration that configures a list-style collection view cell. The cell provider receives an item identifier, looks up the matching product model, and sets the cell’s text and secondary text.

Important: The collection view must have a compatible layout. For a list-style layout, you can configure a UICollectionViewCompositionalLayout.list(using:) layout when creating the collection view. If your project uses a custom cell, replace the registration with the appropriate cell configuration.

Step 5: Load the Initial Data

private func loadProducts() {
    let first = Product(
        id: UUID(),
        name: "Wireless Headphones",
        price: 79.99
    )

    let second = Product(
        id: UUID(),
        name: "Bluetooth Speaker",
        price: 49.50
    )

    let third = Product(
        id: UUID(),
        name: "USB-C Cable",
        price: 12.99
    )

    products = [
        first.id: first,
        second.id: second,
        third.id: third
    ]

    var snapshot =
        NSDiffableDataSourceSnapshot<Section, UUID>()

    snapshot.appendSections([.main])
    snapshot.appendItems(
        [first.id, second.id, third.id],
        toSection: .main
    )

    dataSource.apply(
        snapshot,
        animatingDifferences: false
    )
}

The method stores the model objects in a dictionary, creates a snapshot, adds one section, and appends the product IDs in display order. Applying the snapshot populates the collection view.

Step 6: Call the Setup Methods

override func viewDidLoad() {
    super.viewDidLoad()

    configureDataSource()
    loadProducts()
}

Call these methods after the collection view has been initialized and configured. This ensures the data source is ready before the first snapshot is applied.

5. Updating the UI by Applying Snapshots

The central operation in diffable data sources is applying a snapshot. The snapshot represents the new intended state; the diffable data source compares it with the current state and updates the interface accordingly.

var snapshot =
    NSDiffableDataSourceSnapshot<Section, UUID>()

snapshot.appendSections([.main])
snapshot.appendItems(productIDs, toSection: .main)

dataSource.apply(snapshot, animatingDifferences: true)

When animatingDifferences is true, UIKit animates the changes where appropriate. When it is false, the UI is updated without animated differences. Choose the behavior based on the screen and the type of update.

5.1 Adding a New Item

private func addProduct(name: String, price: Double) {
    let product = Product(
        id: UUID(),
        name: name,
        price: price
    )

    products[product.id] = product

    var snapshot = dataSource.snapshot()
    snapshot.appendItems([product.id], toSection: .main)

    dataSource.apply(snapshot, animatingDifferences: true)
}

The new product is stored in the model dictionary, and its identifier is added to the current snapshot. The diffable data source determines where the new item belongs in the UI.

5.2 Deleting an Item

private func deleteProduct(id: UUID) {
    guard dataSource.snapshot().itemIdentifiers.contains(id)
    else {
        return
    }

    var snapshot = dataSource.snapshot()
    snapshot.deleteItems([id])

    dataSource.apply(snapshot, animatingDifferences: true) {
        self.products.removeValue(forKey: id)
    }
}

The item is removed from the snapshot, and the completion closure removes the corresponding model from the dictionary after the UI update finishes. For more complex apps, coordinate model updates and snapshot application through a single source of truth so that both remain consistent.

5.3 Moving an Item

private func moveProduct(
    id: UUID,
    before otherID: UUID
) {
    var snapshot = dataSource.snapshot()

    guard snapshot.itemIdentifiers.contains(id),
          snapshot.itemIdentifiers.contains(otherID),
          id != otherID else {
        return
    }

    snapshot.moveItem(id, beforeItem: otherID)
    dataSource.apply(snapshot, animatingDifferences: true)
}

Movement operations work with identifiers rather than array indexes. This makes the operation easier to reason about when items are inserted or deleted. If your application stores an explicit ordering in a database or model, update that ordering too.

6. Updating the Content of an Existing Item

Changing an item’s model properties does not automatically notify the diffable data source. The application must tell the data source that the visible content needs to be refreshed.

For example, if a product’s price changes but its ID remains the same, update the model and then mark the item for reconfiguration or reload.

Using reconfigureItems(_:)

private func updateProductPrice(
    id: UUID,
    newPrice: Double
) {
    guard var product = products[id] else {
        return
    }

    product.price = newPrice
    products[id] = product

    var snapshot = dataSource.snapshot()

    if snapshot.itemIdentifiers.contains(id) {
        snapshot.reconfigureItems([id])
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

reconfigureItems(_:) is useful when an existing item needs new content and the existing cell can be configured again. It preserves the existing cell where supported, which can help avoid unnecessary cell replacement.

Using reloadItems(_:)

var snapshot = dataSource.snapshot()
snapshot.reloadItems([productID])
dataSource.apply(snapshot, animatingDifferences: true)

reloadItems(_:) asks UIKit to reload the specified items. Use it when a reload is more appropriate for the change. The identifiers must already exist in the snapshot.

Use the API appropriate for your deployment target and cell behavior. In both cases, update the model data before applying the snapshot so that the cell provider reads the latest values.

7. Updating a UITableView with a Diffable Data Source

Diffable data sources are not limited to collection views. The same snapshot-based approach can be used with table views.

enum ListSection {
    case tasks
}

struct TaskItem: Hashable {
    let id: UUID
    var title: String
    var isCompleted: Bool
}

private var tasks: [UUID: TaskItem] = [:]

private var tableDataSource:
    UITableViewDiffableDataSource<ListSection, UUID>!

Configure the table view’s diffable data source with a cell provider:

private func configureTableDataSource() {
    tableDataSource = UITableViewDiffableDataSource<
        ListSection, UUID
    >(tableView: tableView) {
        [weak self] tableView, indexPath, taskID in

        guard let task = self?.tasks[taskID] else {
            return nil
        }

        let cell = tableView.dequeueReusableCell(
            withIdentifier: "TaskCell",
            for: indexPath
        )

        var content = cell.defaultContentConfiguration()
        content.text = task.title
        content.secondaryText = task.isCompleted
            ? "Completed"
            : "Pending"
        cell.contentConfiguration = content

        return cell
    }
}

Ensure the table view cell identifier is registered or configured correctly in your storyboard or code. To display tasks, create a snapshot, append the .tasks section and task IDs, and apply the snapshot through tableDataSource.apply(_:animatingDifferences:).

8. Search and Filtering with Diffable Data Sources

Search and filtering are common scenarios in dynamic lists. When the user types into a search field, filter the underlying model collection, then apply a snapshot containing only the matching identifiers.

private func applySearch(_ query: String) {
    let normalizedQuery = query.trimmingCharacters(
        in: .whitespacesAndNewlines
    )

    let filteredIDs = products.values
        .filter {
            normalizedQuery.isEmpty ||
            $0.name.localizedCaseInsensitiveContains(
                normalizedQuery
            )
        }
        .sorted { $0.name < $1.name }
        .map(\.id)

    var snapshot =
        NSDiffableDataSourceSnapshot<Section, UUID>()

    snapshot.appendSections([.main])
    snapshot.appendItems(filteredIDs, toSection: .main)

    dataSource.apply(snapshot, animatingDifferences: true)
}

This example filters products by name, sorts the matches alphabetically, and creates a snapshot containing the matching product IDs. The model dictionary still contains all products, so clearing the search query restores the complete list.

For larger data sets, consider debouncing frequent search input, filtering on a background queue where appropriate, and keeping UI updates on a consistent execution context. Make sure that the results you apply correspond to the most recent search query.

9. Multiple Sections in a Snapshot

Diffable data sources support multiple sections, making them suitable for interfaces grouped by category, status, date, or content type.

enum ProductSection: Hashable {
    case featured
    case regular
}

var snapshot =
    NSDiffableDataSourceSnapshot<ProductSection, UUID>()

snapshot.appendSections([.featured, .regular])
snapshot.appendItems(featuredProductIDs, toSection: .featured)
snapshot.appendItems(regularProductIDs, toSection: .regular)

dataSource.apply(snapshot, animatingDifferences: true)

The section identifiers describe the groups, while the item identifiers describe the individual products. Every item identifier should occur only once in the snapshot, including across sections.

For a section that contains hierarchical or expandable content, investigate NSDiffableDataSourceSectionSnapshot. It supports hierarchical section structures for appropriate table and collection view designs.

10. Efficient UI Updates and Performance

Diffable data sources simplify update coordination, but efficient performance still depends on how data is prepared and how frequently snapshots are applied.

Best Practices

  • Use stable IDs: Prefer UUIDs or persistent database identifiers rather than generating identifiers during every refresh.
  • Keep model data separate: Store complete models in a model layer or dictionary and use IDs in the snapshot.
  • Update the model first: Ensure the cell provider can read the latest values before applying the new snapshot.
  • Batch related changes: Combine several additions and deletions into one snapshot when practical rather than applying many snapshots in rapid succession.
  • Avoid unnecessary refreshes: Reconfigure or reload only the items whose visible content changed.
  • Keep identifiers unique: Duplicate section or item identifiers can cause errors or inconsistent results.
  • Use appropriate animations: Animated updates improve clarity, but frequent or large animations may distract users or affect responsiveness.
  • Keep UI updates consistent: Diffable data source APIs are main-actor-oriented in current UIKit interfaces. Keep snapshot application on the main actor unless using an API and execution model that explicitly support a different queue, and use one consistent approach.
  • Manage asynchronous results: If network responses arrive out of order, prevent stale results from replacing a newer UI state.
  • Profile large lists: Test with realistic data sizes and use Instruments to find expensive model processing or cell configuration.

Understanding the Cost of Applying Snapshots

Applying a snapshot with animated differences requires UIKit to compare the current state with the new state. The work grows with the size of the snapshot, so frequent updates across a very large collection may become expensive. The snapshot API helps simplify update logic; it does not eliminate the cost of preparing data, comparing states, or rendering cells.

Use applySnapshotUsingReloadData(_:) when you intentionally want to reset the view to a snapshot without computing a diff or animating individual changes. It is generally not the preferred choice for ordinary incremental updates where preserving a smooth transition is important.

11. Common Errors and Troubleshooting

Problem Likely Cause Solution
Duplicate identifier error or inconsistent state Two items use the same identifier in one snapshot. Use unique, stable IDs and verify the snapshot contents.
Updated text does not appear The model changed but the existing item was not refreshed. Update the model, then call reconfigureItems or reloadItems.
Cell provider cannot find a model The snapshot contains an ID missing from the model store. Keep model storage and snapshot identifiers synchronized.
Unexpected insert/delete animations Identifiers are recreated when the data refreshes. Preserve each logical item’s identifier across updates.
Wrong items appear after filtering The filter uses stale data or asynchronous results arrive out of order. Apply results for the current query and keep a reliable source of truth.
Snapshot update exception Invalid identifiers, overlapping updates, or inconsistent update coordination. Use the current snapshot, validate identifiers, and centralize snapshot application.
Cell shows old content The cell configuration reads stale model values. Update the model before applying the snapshot and configure the cell from the current model.

12. Practical Project: Product List with Add, Delete, and Refresh

Project goal: Build a product list screen that uses a collection view and diffable data source to display products and update them safely.

Required Features

  • Display a list of products with names and prices.
  • Load an initial set of sample products.
  • Add a product using a button or form.
  • Delete a product using a user action.
  • Change the price of an existing product.
  • Refresh the changed item using reconfigureItems(_:) where supported.
  • Animate insertions and deletions using snapshots.
  • Add a search field to filter the visible products.

Suggested Development Steps

  1. Create a UIKit project in Xcode and add a collection view.
  2. Define a Product model with a stable UUID.
  3. Create a dictionary or repository to store products by ID.
  4. Configure the collection view and diffable data source.
  5. Build and apply the initial snapshot.
  6. Implement add and delete actions by modifying the snapshot.
  7. Implement price changes by updating the model and reconfiguring the item.
  8. Add search and verify that clearing the search restores all products.
  9. Test empty data, duplicate IDs, repeated updates, and large product lists.

Expected Result

The interface should reflect the latest product state without manually coordinating every row or item insertion and deletion. Added and removed products should update correctly, changed prices should appear, and filtering should display only matching items.

13. Practice Exercises

  1. Create a task list using UITableViewDiffableDataSource.
  2. Add a new section called “Completed” and move completed tasks into it.
  3. Implement deletion of a selected product using its stable identifier.
  4. Update a product title and refresh only that item.
  5. Build a search field that filters a collection view by product name.
  6. Compare applying a snapshot with animations enabled and disabled.
  7. Test what happens when a new UUID is generated for every refresh, then correct the design to use stable IDs.

14. Interview Questions and Answers

Q1. What is a diffable data source in iOS?

Answer: It is a UIKit data source that manages section and item identifiers and automatically determines the differences between the current UI state and a new snapshot.

Q2. What is an NSDiffableDataSourceSnapshot?

Answer: It represents the intended state of a table view or collection view, including section identifiers, item identifiers, and their ordering.

Q3. Why do identifiers need to conform to Hashable?

Answer: The diffable data source uses hashable and equatable identifiers to track items and compare snapshots efficiently. Identifiers must also be unique within the snapshot’s relevant item or section collection.

Q4. What is the purpose of apply(_:animatingDifferences:)?

Answer: It applies a snapshot to the data source so that the UI reflects the snapshot’s state, optionally animating the changes.

Q5. Does changing a model property automatically update a visible cell?

Answer: No. The application must notify the data source that the existing item needs a refresh, for example by using reconfigureItems(_:) or reloadItems(_:).

Q6. What is the difference between reloadItems and reconfigureItems?

Answer: Both refresh existing item content, but reconfiguration is designed to update content while preserving the existing cell where supported. Reloading requests a reload of the specified items.

Q7. Can diffable data sources be used with UITableView?

Answer: Yes. Use UITableViewDiffableDataSource with an NSDiffableDataSourceSnapshot.

Q8. What causes unexpected insert and delete animations?

Answer: A common cause is generating new identifiers for items that already exist. Stable IDs allow the data source to recognize the same logical item across snapshots.

Q9. Does a diffable data source automatically observe changes in the model?

Answer: No. The app must detect model changes, prepare a new snapshot, and apply it.

Q10. When should applySnapshotUsingReloadData be used?

Answer: Use it when intentionally resetting the view to a snapshot without computing a diff or animating individual changes, rather than for routine incremental updates.

15. Quick Revision Checklist

  • Understand the role of UITableViewDiffableDataSource and UICollectionViewDiffableDataSource.
  • Use NSDiffableDataSourceSnapshot to represent the intended UI state.
  • Use stable, unique, hashable section and item identifiers.
  • Configure a cell provider that reads the current model data.
  • Apply snapshots to insert, delete, and reorder items.
  • Update model values before refreshing existing cells.
  • Choose between reconfiguration, reloading, and full reload based on the update.
  • Batch changes and avoid unnecessary snapshot applications.
  • Handle asynchronous filtering and data refreshes carefully.
  • Test edge cases and measure performance with realistic data.

16. Conclusion

Diffable data sources provide a clear and reliable approach to managing dynamic table views and collection views in UIKit. By describing the desired UI state through snapshots and identifying items with stable IDs, developers can avoid much of the manual coordination required by traditional data sources.

For effective implementation, keep model data synchronized with identifiers, apply snapshots consistently, and refresh existing items when their displayed content changes. Combined with thoughtful cell configuration and performance testing, diffable data sources make complex list updates easier to maintain and understand.

17. Additional Learning Resources

Previous Next
whatsapp